﻿/*--------------------------------------------------------------
 * Author : Shine K Velayudhan 
 ---------------------------------------------------------------*/
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.Text;

namespace IQN.Cache
{
    /// <summary>
    /// ICahce implementation. This class contains basic methods for cache manipulation.
    /// </summary>
    public class Cache : ICache
    {
        private readonly string _baseFolder = string.Empty;
        public Cache(string baseFolder)
        {
            if (null == baseFolder)
            {
                throw new ArgumentNullException("baseFolder");
            }
            this._baseFolder = baseFolder;
            if (!Directory.Exists(_baseFolder))
            {
                Directory.CreateDirectory(_baseFolder);
            }
        }
        private static string ComputeHash(string key)
        {
            if (null == key)
            {
                return "NULL";
            }
            byte[] bytes = new ASCIIEncoding().GetBytes(key);
            MD5 md = new MD5CryptoServiceProvider();
            Guid guid = new Guid(md.ComputeHash(bytes));
            return guid.ToString();
        }
        private string GetHashBucketFileName(string hashValue)
        {
            return (this._baseFolder + @"\" + hashValue + ".bkt");
        }
        private byte[] ObjectToByteArray<T>(T t)
        {
            if (t == null)
                return null;
            BinaryFormatter bf = new BinaryFormatter();
            MemoryStream ms = new MemoryStream();
            bf.Serialize(ms, t);
            return ms.ToArray();
        }
        private T ByteArrayToObject<T>(byte[] arrBytes)
        {
            MemoryStream memStream = new MemoryStream();
            BinaryFormatter binForm = new BinaryFormatter();
            memStream.Write(arrBytes, 0, arrBytes.Length);
            memStream.Seek(0, SeekOrigin.Begin);
            T t = (T)binForm.Deserialize(memStream);
            return t;
        }
        void ICache.Add<T>(T t, int expirationInMinutes, string id)
        {
            if (null == t)
            {
                throw new ArgumentNullException("t");
            }
            byte[] content = ObjectToByteArray(t);
            string key = typeof(T).ToString();
            string hashValue = ComputeHash(key);
            string hashBucketFileName = this.GetHashBucketFileName(hashValue);
            HashBucket bucket = File.Exists(hashBucketFileName) ? HashBucket.Load(hashBucketFileName) : new HashBucket();
            BucketEntry item = null;
            if (bucket.TryGetItem(key, out item,id))
            {
                File.Delete(this._baseFolder + @"\" + item.DataFileName);
                bucket.RemoveItem(key,id);
            }
            item = new BucketEntry(key, DateTime.UtcNow.AddMinutes(expirationInMinutes),id);
            bucket.AddItem(item);
            bucket.Save(hashBucketFileName);
            using (Stream stream = File.Create(this._baseFolder + @"\" + item.DataFileName))
            {
                stream.Write(content, 0, content.Length);
            }
        }
        bool ICache.TryGetItem<T>(out T t,string id)
        {   
            t = default(T);
            byte[] content = null;
            string key = typeof(T).ToString();
            string hashValue = ComputeHash(key);
            string hashBucketFileName = this.GetHashBucketFileName(hashValue);
            if (!File.Exists(hashBucketFileName))
            {
                return false;
            }
            HashBucket bucket = HashBucket.Load(hashBucketFileName);
            BucketEntry item = null;
            if (!bucket.TryGetItem(key, out item,id))
            {
                return false;
            }
            if ((item.AbsoluteExpiration <= DateTime.UtcNow))
            {
                ((ICache)this).Remove<T>(id);
                return false;
            }
            if ((item.Id != id))
            {
                return false;
            }
            string path = this._baseFolder + @"\" + item.DataFileName;
            if (!File.Exists(path))
            {
                return false;
            }
            File.SetLastAccessTime(hashBucketFileName, DateTime.UtcNow);
            item.LastAccessedOn = DateTime.UtcNow;
            bucket.Save(hashBucketFileName);
            content = File.ReadAllBytes(path);
            t = ByteArrayToObject<T>(content);
            return true;
        }
        void ICache.Remove<T>(string id)
        {
            string key = typeof(T).ToString();
            string hashValue = ComputeHash(key);
            string hashBucketFileName = this.GetHashBucketFileName(hashValue);
            if (File.Exists(hashBucketFileName))
            {
                HashBucket bucket = HashBucket.Load(hashBucketFileName);
                BucketEntry item = null;
                if (bucket.TryGetItem(key, out item,id))
                {
                    bucket.RemoveItem(key,id);
                    if (bucket.ItemCount == 0)
                    {
                        File.Delete(hashBucketFileName);
                    }
                    else
                    {
                        bucket.Save(hashBucketFileName);
                    }
                    string path = this._baseFolder + @"\" + item.DataFileName;
                    if (File.Exists(path))
                    {
                        File.Delete(path);
                    }
                }
            }
        }
        void ICache.ClearAll()
        {
            try
            {
                string[] files = Directory.GetFiles(this._baseFolder);
                for(int i = 0;i< files.Length;i++)
                {
                    if(files[i].EndsWith(".bkt") || files[i].EndsWith(".dat"))
                    {
                        File.Delete(files[i]);
                    }
                }
            }
            catch{}
        }


        void ICache.Add<T>(T t, int expirationInMinutes)
        {
            (this as ICache).Add(t,expirationInMinutes,string.Empty);
        }

        void ICache.Remove<T>()
        {
            (this as ICache).Remove<T>(string.Empty);
        }

        bool ICache.TryGetItem<T>(out T t)
        {
            return (this as ICache).TryGetItem(out t, string.Empty);
        }
    }
}
